using System;
using System.Collections;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Threading;

namespace ICommonLibrary.Collection {

    [Serializable]
    public class SoftLimitMRUCache : IDeserializationCallback {

        private const int DefaultStrongRefCount = 128;
        private object _syncRoot;

        private readonly int _strongReferenceCount;

        // actual cache of the entries.  soft references are used for both the keys and the
        // values here since the values pertaining to the MRU entries are kept in a
        // separate hard reference cache (to avoid their enqueuement/garbage-collection).
        [NonSerialized]
        private readonly IDictionary _softReferenceCache = new WeakHashtable();

        // the MRU cache used to keep hard references to the most recently used query plans;
        // note : LRU here is a bit of a misnomer, it indicates that LRU entries are removed, the
        // actual kept entries are the MRU entries
        [NonSerialized]
        private LRUMap _strongReferenceCache;

        public SoftLimitMRUCache(int strongReferenceCount) {
            _strongReferenceCount = strongReferenceCount;
            _strongReferenceCache = new LRUMap(strongReferenceCount);
        }

        public SoftLimitMRUCache()
            : this(DefaultStrongRefCount) { }

        private object SyncRoot {
            get {
                if (_syncRoot == null)
                    Interlocked.CompareExchange(ref _syncRoot, new object(), null);

                return _syncRoot;
            }
        }

        void IDeserializationCallback.OnDeserialization(object sender) {
            _strongReferenceCache = new LRUMap(_strongReferenceCount);
        }

        public object this[object key] {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get {
                lock (SyncRoot) {
                    object result = _softReferenceCache[key];
                    if (result != null) {
                        _strongReferenceCache.Add(key, result);
                    }
                    return result;
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Put(object key, object value) {
            lock (SyncRoot) {
                _softReferenceCache[key] = value;
                _strongReferenceCache[key] = value;
            }
        }

        public int Count {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get {
                lock (SyncRoot) {
                    return _strongReferenceCache.Count;
                }
            }
        }

        public int SoftCount {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get {
                lock (SyncRoot) {
                    return _softReferenceCache.Count;
                }
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void Clear() {
            lock (SyncRoot) {
                _strongReferenceCache.Clear();
                _softReferenceCache.Clear();
            }
        }
    }
}